package picounit.misc;

import moved.org.objectweb.asm.AnnotationVisitor;
import moved.org.objectweb.asm.Attribute;
import moved.org.objectweb.asm.ClassAdapter;
import moved.org.objectweb.asm.ClassReader;
import moved.org.objectweb.asm.ClassVisitor;
import moved.org.objectweb.asm.FieldVisitor;
import moved.org.objectweb.asm.Label;
import moved.org.objectweb.asm.MethodAdapter;
import moved.org.objectweb.asm.MethodVisitor;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;

public class TraceClassDelegate extends ClassAdapter {
	private final PrintStream printStream;
	private final OpcodeLookup opcodeLookup = new OpcodeLookup();

	public TraceClassDelegate(ClassVisitor classVisitor) {
		this(System.out, classVisitor);
	}

	public TraceClassDelegate(PrintStream printStream, ClassVisitor classVisitor) {
		super(classVisitor);

		this.printStream = printStream;
	}
	
	public void trace(byte[] byteCode) throws IOException {
		trace(new ByteArrayInputStream(byteCode));
	}

	public void trace(InputStream inputStream) throws IOException {
		new ClassReader(inputStream).accept(this, false);
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// ClassVisitor
	///////////////////////////////////////////////////////////////////////////////////////////////

	@Override
	public void visitSource(String source, String debug) {
		print(source, debug);

		super.visitSource(source, debug);
	}

	@Override
	public void visit(int version, int access, String name, String signature, String superName,
		String[] interfaces) {
		
		print(version, access, name, signature, superName);
		
		super.visit(version, access, name, signature, superName, interfaces);
	}

	@Override
	public void visitAttribute(Attribute attr) {
		print(attr);
		
		super.visitAttribute(attr);
	}

	@Override
	public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
		print(desc, visible);
		
		return new TraceAnnotationDelegate(super.visitAnnotation(desc, visible));
	}

	@Override
	public FieldVisitor visitField(int access, String name, String desc, String signature,
		Object value) {
		
		print(access, name, desc, signature);
		
		return new TraceFieldDelegate(super.visitField(access, name, desc, signature, value));
	}

	@Override
	public void visitInnerClass(String name, String outerName, String innerName, int access) {
		print(name, outerName, innerName, access);

		super.visitInnerClass(name, outerName, innerName, access);
	}
	
	@Override
	public MethodVisitor visitMethod(int access, String name, String desc, String signature,
		String[] exceptions) {
		
		print(access, name, desc, signature, exceptions);
		
		return new TraceMethodDelegate(
			super.visitMethod(access, name, desc, signature, exceptions));
	}

	@Override
	public void visitOuterClass(String owner, String name, String desc) {
		print(owner, name, desc);
		
		super.visitOuterClass(owner, name, desc);
	}

	@Override
	public void visitEnd() {
		print();
		
		super.visitEnd();
	}

	public class TraceAnnotationDelegate implements AnnotationVisitor {
		private final AnnotationVisitor annotationVisitor;

		public TraceAnnotationDelegate(AnnotationVisitor annotationVisitor) {
			this.annotationVisitor = annotationVisitor;
		}
		
		public AnnotationVisitor visitAnnotation(String name, String desc) {
			print(name, desc);

			return new TraceAnnotationDelegate(annotationVisitor.visitAnnotation(name, desc));
		}

		public void visit(String name, Object value) {
			print(name, value);

			annotationVisitor.visit(name, value);
		}

		public void visitEnum(String name, String desc, String value) {
			print(name, desc, value);
			
			annotationVisitor.visitEnum(name, desc, value);
		}

		public AnnotationVisitor visitArray(String name) {
			print(name);

			return new TraceAnnotationDelegate(annotationVisitor.visitArray(name));
		}

		public void visitEnd() {
			print();
			
			annotationVisitor.visitEnd();
		}

		private void print(Object ... args) {
			printMethod(new Throwable().getStackTrace()[1].getMethodName(), args);
		}
	}
	
	public class TraceFieldDelegate implements FieldVisitor {
		private final FieldVisitor fieldVisitor;

		public TraceFieldDelegate(FieldVisitor fieldVisitor) {
			this.fieldVisitor = fieldVisitor;
		}

		public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			print(desc, visible);

			return new TraceAnnotationDelegate(fieldVisitor.visitAnnotation(desc, visible));
		}

		public void visitAttribute(Attribute attr) {
			print(attr);
			
			fieldVisitor.visitAttribute(attr);
		}

		public void visitEnd() {
			fieldVisitor.visitEnd();
		}

		private void print(Object ... args) {
			printMethod(new Throwable().getStackTrace()[1].getMethodName(), args);
		}
	}
	
	public class TraceMethodDelegate extends MethodAdapter {
		public TraceMethodDelegate(MethodVisitor methodVisitor) {
			super(methodVisitor);
		}

		@Override
		public void visitCode() {
			print();

			super.visitCode();
		}

		@Override
		public void visitAttribute(Attribute attr) {
			print(attr);
			
			super.visitAttribute(attr);
		}
		
		@Override
		public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			print(desc, visible);
			
			return new TraceAnnotationDelegate(super.visitAnnotation(desc, visible));
		}

		@Override
		public AnnotationVisitor visitAnnotationDefault() {
			print();
			
			return new TraceAnnotationDelegate(super.visitAnnotationDefault());
		}

		@Override
		public void visitFieldInsn(int opcode, String owner, String name, String desc) {
			print(opcode, owner, name, desc);
			
			super.visitFieldInsn(opcode, owner, name, desc);
		}

		@Override
		public void visitIincInsn(int var, int increment) {
			print(var, increment);
			
			super.visitIincInsn(var, increment);
		}

		@Override
		public void visitInsn(int opcode) {
			print(opcode);
			
			super.visitInsn(opcode);
		}

		@Override
		public void visitIntInsn(int opcode, int operand) {
			print(opcode, operand);
			
			super.visitIntInsn(opcode, operand);
		}

		@Override
		public void visitJumpInsn(int opcode, Label label) {
			print(opcode, label);
			
			super.visitJumpInsn(opcode, label);
		}

		@Override
		public void visitLabel(Label label) {
//			print(label);
			
			super.visitLabel(label);
		}

		@Override
		public void visitLdcInsn(Object cst) {
			print(cst);
			
			super.visitLdcInsn(cst);
		}

		@Override
		public void visitLineNumber(int line, Label start) {
//			print(line, start);
			
			super.visitLineNumber(line, start);
		}

		@Override
		public void visitLocalVariable(String name, String desc, String signature, Label start, Label end,
			int index) {

			print(name, desc, signature, start, end, index);
			
			super.visitLocalVariable(name, desc, signature, start, end, index);
		}

		@Override
		public void visitLookupSwitchInsn(Label dflt, int keys[], Label labels[]) {
			print(dflt, keys, labels);
			
			super.visitLookupSwitchInsn(dflt, keys, labels);
		}

		@Override
		public void visitMaxs(int maxStack, int maxLocals) {
			print(maxStack, maxLocals);
			
			super.visitMaxs(maxStack, maxLocals);
		}

		@Override
		public void visitMethodInsn(int opcode, String owner, String name, String desc) {
			print(opcode, owner, name, desc);
			
			super.visitMethodInsn(opcode, owner, name, desc);
		}

		@Override
		public void visitMultiANewArrayInsn(String desc, int dims) {
			print(desc, dims);
			
			super.visitMultiANewArrayInsn(desc, dims);
		}

		@Override
		public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
			print(parameter, desc, visible);

			return new TraceAnnotationDelegate(
				super.visitParameterAnnotation(parameter, desc, visible));
		}

		@Override
		public void visitTableSwitchInsn(int min, int max, Label dflt, Label labels[]) {
			print(min, max, dflt, labels);

			super.visitTableSwitchInsn(min, max, dflt, labels);
		}

		@Override
		public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
			print(start, end, handler, type);
			
			super.visitTryCatchBlock(start, end, handler, type);
		}

		@Override
		public void visitTypeInsn(int opcode, String desc) {
			print(opcode, desc);
			
			super.visitTypeInsn(opcode, desc);
		}

		@Override
		public void visitVarInsn(int opcode, int var) {
			print(opcode, var);
			
			super.visitVarInsn(opcode, var);
		}
		
		@Override
		public void visitEnd() {
			print();
			
			super.visitEnd();
		}

		private void print(Object ... args) {
			printMethod(new Throwable().getStackTrace()[1].getMethodName(), args);
		}
	}

	private void print(Object ... args) {
		printMethod(new Throwable().getStackTrace()[1].getMethodName(), args);
	}

	private void printMethod(String methodName, Object... args) {
		printStream.print(methodName + "(");
		
		if (args != null) {
			for (int index = 0; index < args.length; ++index) {
				if (index != 0) {
					printStream.print(", ");
				}

				Object arg = args[index];
				
				printStream.print(arg);
			}
		}
		
		printStream.println(')');
	}
}
